﻿// Copyright (c) 2014 Converter Systems LLC

using ConverterSystems.ServiceModel;
using Microsoft.Expression.Interactivity.Core;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell.Settings;
using Opc.Ua;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.ServiceModel.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

namespace ConverterSystems.UaBrowser.ViewModels
{
    [Export]
    public class UaBrowserViewModel : INotifyPropertyChanged, IDisposable
    {
        private const string CollectionPath = "UaBrowser";
        private const string EventFormatBasicProperty = "EventFormatBasic";
        private const string EventFormatCSharpProperty = "EventFormatCSharp";
        private const string HistoryProperty = "History";
        private const string ReadOnlyValueFormatBasicProperty = "ReadOnlyValueFormatBasic";
        private const string ReadOnlyValueFormatCSharpProperty = "ReadOnlyValueFormatCSharp";
        private const string ValueFormatBasicProperty = "ValueFormatBasic";
        private const string ValueFormatCSharpProperty = "ValueFormatCSharp";
        private const string MethodFormatBasicProperty = "MethodFormatBasic";
        private const string MethodFormatCSharpProperty = "MethodFormatCSharp";
        private ApplicationConfiguration _config;
        private CancellationTokenSource _cts;
        private string _endpointUrl;
        private string _EventFormatBasic;
        private string _EventFormatCSharp;
        private ObservableCollection<string> _history;
        private EnvDTE.DTE _ide;
        private SemaphoreSlim _lock;
        private ObservableCollection<ReferenceDescriptionViewModel> _namespaceItems;
        private string _ReadOnlyValueFormatBasic;
        private string _ReadOnlyValueFormatCSharp;
        private UaSessionClient _session;
        private WritableSettingsStore _store;
        private string _ValueFormatBasic;
        private string _ValueFormatCSharp;
        private string _MethodFormatBasic;
        private string _MethodFormatCSharp;

        public UaBrowserViewModel()
        { }

        [ImportingConstructor]
        public UaBrowserViewModel(Microsoft.VisualStudio.Shell.SVsServiceProvider vsServiceProvider)
        {
            _cts = new CancellationTokenSource();
            _lock = new SemaphoreSlim(1);
            _config = new UaConfiguration();
            RefreshCommand = new ActionCommand(async (o) => await RefreshAsync(o));
            BrowseStopCommand = new ActionCommand(BrowseStop);
            SaveSettingsCommand = new ActionCommand(SaveSettings);
            ResetSettingsCommand = new ActionCommand(ResetSettings);
            NamespaceItems = new ObservableCollection<ReferenceDescriptionViewModel>();
            _ide = (EnvDTE.DTE)vsServiceProvider.GetService(typeof(EnvDTE.DTE));
            var shellSettingsManager = new ShellSettingsManager(vsServiceProvider);
            _store = shellSettingsManager.GetWritableSettingsStore(SettingsScope.UserSettings);

            LoadSettings();
            LoadHistory();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public ICommand BrowseStopCommand { get; private set; }

        public string EndpointUrl
        {
            get { return _endpointUrl; }
            private set { _endpointUrl = value; NotifyPropertyChanged(); }
        }

        public string EventFormatBasic
        {
            get { return _EventFormatBasic; }
            set { _EventFormatBasic = value; NotifyPropertyChanged(); }
        }

        public string EventFormatCSharp
        {
            get { return _EventFormatCSharp; }
            set { _EventFormatCSharp = value; NotifyPropertyChanged(); }
        }

        public string MethodFormatBasic
        {
            get { return _MethodFormatBasic; }
            set { _MethodFormatBasic = value; NotifyPropertyChanged(); }
        }

        public string MethodFormatCSharp
        {
            get { return _MethodFormatCSharp; }
            set { _MethodFormatCSharp = value; NotifyPropertyChanged(); }
        }

        public ObservableCollection<string> History
        {
            get { return _history; }
            private set { _history = value; NotifyPropertyChanged(); }
        }

        public bool IsLoading
        {
            get { return _lock.CurrentCount == 0; }
        }

        public ObservableCollection<ReferenceDescriptionViewModel> NamespaceItems
        {
            get { return _namespaceItems; }
            private set { _namespaceItems = value; NotifyPropertyChanged(); }
        }

        public string ReadOnlyValueFormatBasic
        {
            get { return _ReadOnlyValueFormatBasic; }
            set { _ReadOnlyValueFormatBasic = value; NotifyPropertyChanged(); }
        }

        public string ReadOnlyValueFormatCSharp
        {
            get { return _ReadOnlyValueFormatCSharp; }
            set { _ReadOnlyValueFormatCSharp = value; NotifyPropertyChanged(); }
        }

        public ICommand RefreshCommand { get; private set; }

        public ICommand ResetSettingsCommand { get; private set; }

        public ICommand SaveSettingsCommand { get; private set; }

        public string ValueFormatBasic
        {
            get { return _ValueFormatBasic; }
            set { _ValueFormatBasic = value; NotifyPropertyChanged(); }
        }

        public string ValueFormatCSharp
        {
            get { return _ValueFormatCSharp; }
            set { _ValueFormatCSharp = value; NotifyPropertyChanged(); }
        }

        public void Dispose()
        {
            if (_cts != null)
            {
                _cts.Cancel();
                _cts.Dispose();
                _cts = null;
            }
            if (_session != null)
            {
                _session.Dispose();
                _session = null;
            }
            SaveHistory();
        }

        public string FormatProperty(ReferenceDescriptionViewModel vm)
        {
            if (_ide == null) return string.Empty;
            var doc = _ide.ActiveDocument;
            if (doc == null) return string.Empty;
            var codeModel = doc.ProjectItem.FileCodeModel;
            if (codeModel == null) return string.Empty;
            var language = codeModel.Language;
            switch (language)
            {
                case EnvDTE.CodeModelLanguageConstants.vsCMLanguageCSharp:
                    if (vm.IsVariable)
                    {
                        if ((vm.AccessLevel & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite)
                        {
                            return FormatSnippet(ValueFormatCSharp, vm.DisplayName, vm.Parent.DisplayName, FormatTypeName(vm.DataType, language), vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                        }
                        return FormatSnippet(ReadOnlyValueFormatCSharp, vm.DisplayName, vm.Parent.DisplayName, FormatTypeName(vm.DataType, language), vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                    }
                    if (vm.IsMethod)
                    {
                        return FormatSnippet(MethodFormatCSharp, vm.DisplayName, vm.Parent.DisplayName, "Func<Object[], Task<Object[]>>", vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                    }
                    if (vm.IsEventNotifier)
                    {
                        return FormatSnippet(EventFormatCSharp, vm.DisplayName, vm.Parent.DisplayName, "EventFieldList", vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                    }
                    break;

                case EnvDTE.CodeModelLanguageConstants.vsCMLanguageVB:
                    if (vm.IsVariable)
                    {
                        if ((vm.AccessLevel & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite)
                        {
                            return FormatSnippet(ValueFormatBasic, vm.DisplayName, vm.Parent.DisplayName, FormatTypeName(vm.DataType, language), vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                        }
                        return FormatSnippet(ReadOnlyValueFormatBasic, vm.DisplayName, vm.Parent.DisplayName, FormatTypeName(vm.DataType, language), vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                    }
                    if (vm.IsMethod)
                    {
                        return FormatSnippet(MethodFormatBasic, vm.DisplayName, vm.Parent.DisplayName, "Func(Of Object(), Task(Of Object()))", vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                    }
                    if (vm.IsEventNotifier)
                    {
                        return FormatSnippet(EventFormatBasic, vm.DisplayName, vm.Parent.DisplayName, "EventFieldList", vm.NodeId.ToString(), vm.Parent.NodeId.ToString(), vm.BrowseName.ToString());
                    }
                    break;
            }
            return string.Empty;
        }

        protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private static string FormatTypeName(Type t, string language)
        {
            if (t == null) return string.Empty;
            if (t.IsArray)
            {
                switch (language)
                {
                    case EnvDTE.CodeModelLanguageConstants.vsCMLanguageCSharp:
                        return t.GetElementType().Name + "[]";

                    case EnvDTE.CodeModelLanguageConstants.vsCMLanguageVB:
                        return t.GetElementType().Name + "()";
                }
            }
            if (!t.IsGenericType) return t.Name;
            if (t.IsNested && t.DeclaringType.IsGenericType) throw new NotImplementedException();
            StringBuilder txt = new StringBuilder();
            switch (language)
            {
                case EnvDTE.CodeModelLanguageConstants.vsCMLanguageCSharp:
                    txt.Append(t.Name, 0, t.Name.IndexOf('`'));
                    txt.Append("<");
                    txt.Append(string.Join(", ", t.GetGenericArguments().Select(arg => FormatTypeName(arg, language))));
                    txt.Append(">");
                    return txt.ToString();

                case EnvDTE.CodeModelLanguageConstants.vsCMLanguageVB:
                    txt.Append(t.Name, 0, t.Name.IndexOf('`'));
                    txt.Append("(Of ");
                    txt.Append(string.Join(", ", t.GetGenericArguments().Select(arg => FormatTypeName(arg, language))));
                    txt.Append(")");
                    return txt.ToString();
            }
            return t.Name;
        }

        private void BrowseStop()
        {
            _cts.Cancel();
            _cts.Dispose();
            _cts = new CancellationTokenSource();
        }

        private string FormatSnippet(string snippet, string name, string parentName, string datatype, string nodeId, string parentNodeId, string browseName)
        {
            var s = new StringBuilder(snippet);
            s.Replace("$name$", name);
            s.Replace("$parentName$", parentName);
            s.Replace("$dataType$", datatype);
            s.Replace("$nodeId$", nodeId);
            s.Replace("$parentNodeId$", parentNodeId);
            s.Replace("$browseName$", browseName);
            s.AppendLine();
            return s.ToString();
        }

        private async Task LoadChildrenAsync(ReferenceDescriptionViewModel parent)
        {
            try
            {
                var token = _cts.Token;
                await _lock.WaitAsync(token);
                NotifyPropertyChanged("IsLoading");
                try
                {
                    do
                    {
                        try
                        {
                            if (_session == null)
                            {
                                _session = new UaSessionClient(_config, _endpointUrl);
                                await _session.OpenAsync();
                            }
                            var children = new List<TreeViewItemViewModel>();
                            var browseRequest = new BrowseRequest { NodesToBrowse = { new BrowseDescription { NodeId = ExpandedNodeId.ToNodeId(parent.NodeId, _session.MessageContext.NamespaceUris), ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, ResultMask = (uint)BrowseResultMask.All, NodeClassMask = (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, BrowseDirection = BrowseDirection.Forward } }, RequestedMaxReferencesPerNode = 5 };
                            var browseResponse = await _session.BrowseAsync(browseRequest, token);
                            var refs = browseResponse.Results.SelectMany(result => result.References).ToArray();
                            if (refs.Length == 0) return;
                            var nodes = refs.Select(r => ExpandedNodeId.ToNodeId(r.NodeId, _session.MessageContext.NamespaceUris)).ToArray();
                            var readTypeRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.DataType }).ToArray() };
                            var readTypeResponse = await _session.ReadAsync(readTypeRequest, token);
                            var readRankRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.ValueRank }).ToArray() };
                            var readRankResponse = await _session.ReadAsync(readRankRequest, token);
                            var readNotifierRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.EventNotifier }).ToArray() };
                            var readNotifierResponse = await _session.ReadAsync(readNotifierRequest, token);
                            var readAccessLevelRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.UserAccessLevel }).ToArray() };
                            var readAccessLevelResponse = await _session.ReadAsync(readAccessLevelRequest, token);
                            NodeId dataTypeNode;
                            Type dataType;
                            int valueRank;
                            byte notifier;
                            byte accessLevel;
                            for (int i = 0; i < refs.Length; i++)
                            {
                                dataTypeNode = readTypeResponse.Results[i].GetValue(NodeId.Null);
                                if (dataTypeNode != NodeId.Null)
                                {
                                    dataType = TypeInfo.GetSystemType(dataTypeNode, _session.MessageContext.Factory);
                                    if (dataType == null)
                                    {
                                        var browseRequest2 = new BrowseRequest { NodesToBrowse = { new BrowseDescription { NodeId = dataTypeNode, ReferenceTypeId = ReferenceTypeIds.HasSubtype, ResultMask = (uint)BrowseResultMask.None, NodeClassMask = (uint)NodeClass.DataType, BrowseDirection = BrowseDirection.Inverse, IncludeSubtypes = false } } };
                                        var browseResponse2 = await _session.BrowseAsync(browseRequest2, token);
                                        var dataTypeRef = browseResponse2.Results[0].References.FirstOrDefault();
                                        if (dataTypeRef != null)
                                        {
                                            dataType = TypeInfo.GetSystemType(ExpandedNodeId.ToNodeId(dataTypeRef.NodeId, _session.MessageContext.NamespaceUris), _session.MessageContext.Factory);
                                        }
                                        if (dataType == null)
                                        {
                                            dataType = typeof(Object);
                                        }
                                    }
                                    valueRank = readRankResponse.Results[i].GetValue(ValueRanks.Any);
                                    if (valueRank == ValueRanks.OneDimension) dataType = dataType.MakeArrayType();
                                    if (valueRank == ValueRanks.TwoDimensions) dataType = dataType.MakeArrayType(2);
                                }
                                else
                                {
                                    dataType = typeof(Object);
                                }
                                notifier = readNotifierResponse.Results[i].GetValue(EventNotifiers.None);
                                accessLevel = readAccessLevelResponse.Results[i].GetValue(AccessLevels.None);
                                children.Add(new ReferenceDescriptionViewModel(refs[i], dataType, accessLevel, notifier, parent, LoadChildrenAsync));
                            }
                            var continuationPoints = new ByteStringCollection(browseResponse.Results.Select(br => br.ContinuationPoint).Where(cp => null != cp));
                            while (continuationPoints.Count > 0)
                            {
                                var browseNextRequest = new BrowseNextRequest { ContinuationPoints = continuationPoints, ReleaseContinuationPoints = false };
                                var browseNextResponse = await _session.BrowseNextAsync(browseNextRequest, token);
                                refs = browseNextResponse.Results.SelectMany(result => result.References).ToArray();
                                if (refs.Length == 0) return;
                                nodes = refs.Select(r => ExpandedNodeId.ToNodeId(r.NodeId, _session.MessageContext.NamespaceUris)).ToArray();
                                readTypeRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.DataType }).ToArray() };
                                readTypeResponse = await _session.ReadAsync(readTypeRequest, token);
                                readRankRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.ValueRank }).ToArray() };
                                readRankResponse = await _session.ReadAsync(readRankRequest, token);
                                readNotifierRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.EventNotifier }).ToArray() };
                                readNotifierResponse = await _session.ReadAsync(readNotifierRequest, token);
                                readAccessLevelRequest = new ReadRequest { NodesToRead = nodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.WriteMask }).ToArray() };
                                readAccessLevelResponse = await _session.ReadAsync(readAccessLevelRequest, token);
                                for (int i = 0; i < refs.Length; i++)
                                {
                                    dataTypeNode = readTypeResponse.Results[i].GetValue(NodeId.Null);
                                    if (dataTypeNode != NodeId.Null)
                                    {
                                        dataType = TypeInfo.GetSystemType(dataTypeNode, _session.MessageContext.Factory);
                                        if (dataType == null)
                                        {
                                            var browseRequest2 = new BrowseRequest { NodesToBrowse = { new BrowseDescription { NodeId = dataTypeNode, ReferenceTypeId = ReferenceTypeIds.HasSubtype, ResultMask = (uint)BrowseResultMask.None, NodeClassMask = (uint)NodeClass.DataType, BrowseDirection = BrowseDirection.Inverse, IncludeSubtypes = false } } };
                                            var browseResponse2 = await _session.BrowseAsync(browseRequest2, token);
                                            var dataTypeRef = browseResponse2.Results[0].References.FirstOrDefault();
                                            if (dataTypeRef != null)
                                            {
                                                dataType = TypeInfo.GetSystemType(ExpandedNodeId.ToNodeId(dataTypeRef.NodeId, _session.MessageContext.NamespaceUris), _session.MessageContext.Factory);
                                            }
                                            if (dataType == null)
                                            {
                                                dataType = typeof(Object);
                                            }
                                        }
                                        valueRank = readRankResponse.Results[i].GetValue(ValueRanks.Any);
                                        if (valueRank == ValueRanks.OneDimension) dataType = dataType.MakeArrayType();
                                        if (valueRank == ValueRanks.TwoDimensions) dataType = dataType.MakeArrayType(2);
                                    }
                                    else
                                    {
                                        dataType = typeof(Object);
                                    }
                                    notifier = readNotifierResponse.Results[i].GetValue(EventNotifiers.None);
                                    accessLevel = readAccessLevelResponse.Results[i].GetValue(AccessLevels.None);
                                    children.Add(new ReferenceDescriptionViewModel(refs[i], dataType, accessLevel, notifier, parent, LoadChildrenAsync));
                                }
                                continuationPoints = new ByteStringCollection(browseNextResponse.Results.Select(br => br.ContinuationPoint).Where(cp => null != cp));
                            }
                            children.ForEach(item => parent.Children.Add(item));
                            break;
                        }
                        catch (OperationCanceledException) { }
                        catch (ServiceResultException ex)
                        {
                            Trace.TraceInformation("ServiceResultException: {0}", ex);
                            if (_session != null)
                            {
                                _session.Dispose();
                                _session = null;
                            }
                            if (ex.Result.Code == StatusCodes.BadSessionIdInvalid)
                            {
                                continue;
                            }
                        }
                        catch (Exception ex)
                        {
                            Trace.TraceInformation("Exception {0}", ex);
                            if (_session != null)
                            {
                                _session.Dispose();
                                _session = null;
                            }
                        }
                        try
                        {
                            await Task.Delay(5000, token);
                        }
                        catch (OperationCanceledException) { }
                    } while (!token.IsCancellationRequested);
                }
                catch (Exception ex) { Trace.TraceInformation("Exception {0}", ex); }
                finally
                {
                    _lock.Release();
                    NotifyPropertyChanged("IsLoading");
                }
            }
            catch (OperationCanceledException) { }
        }

        private void LoadHistory()
        {
            if (_store != null && _store.PropertyExists(CollectionPath, HistoryProperty))
            {
                string value = _store.GetString(CollectionPath, HistoryProperty);
                History = new ObservableCollection<string>(value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries));
            }
            else
            {
                History = new ObservableCollection<string>(new string[] { "opc.tcp://localhost:51212" });
            }
        }

        private void LoadSettings()
        {
            if (_store != null && _store.PropertyExists(CollectionPath, ValueFormatCSharpProperty))
            {
                ValueFormatCSharp = _store.GetString(CollectionPath, ValueFormatCSharpProperty);
            }
            else
            {
                ValueFormatCSharp = Properties.Resources.ValueFormatCSharp;
            }
            if (_store != null && _store.PropertyExists(CollectionPath, EventFormatCSharpProperty))
            {
                EventFormatCSharp = _store.GetString(CollectionPath, EventFormatCSharpProperty);
            }
            else
            {
                EventFormatCSharp = Properties.Resources.EventFormatCSharp;
            }
            if (_store != null && _store.PropertyExists(CollectionPath, MethodFormatCSharpProperty))
            {
                MethodFormatCSharp = _store.GetString(CollectionPath, MethodFormatCSharpProperty);
            }
            else
            {
                MethodFormatCSharp = Properties.Resources.MethodFormatCSharp;
            }
            if (_store != null && _store.PropertyExists(CollectionPath, ReadOnlyValueFormatCSharpProperty))
            {
                ReadOnlyValueFormatCSharp = _store.GetString(CollectionPath, ReadOnlyValueFormatCSharpProperty);
            }
            else
            {
                ReadOnlyValueFormatCSharp = Properties.Resources.ReadOnlyValueFormatCSharp;
            }
            if (_store != null && _store.PropertyExists(CollectionPath, ValueFormatBasicProperty))
            {
                ValueFormatBasic = _store.GetString(CollectionPath, ValueFormatBasicProperty);
            }
            else
            {
                ValueFormatBasic = Properties.Resources.ValueFormatBasic;
            }
            if (_store != null && _store.PropertyExists(CollectionPath, EventFormatBasicProperty))
            {
                EventFormatBasic = _store.GetString(CollectionPath, EventFormatBasicProperty);
            }
            else
            {
                EventFormatBasic = Properties.Resources.EventFormatBasic;
            }
            if (_store != null && _store.PropertyExists(CollectionPath, MethodFormatBasicProperty))
            {
                MethodFormatBasic = _store.GetString(CollectionPath, MethodFormatBasicProperty);
            }
            else
            {
                MethodFormatBasic = Properties.Resources.MethodFormatBasic;
            }
            if (_store != null && _store.PropertyExists(CollectionPath, ReadOnlyValueFormatBasicProperty))
            {
                ReadOnlyValueFormatBasic = _store.GetString(CollectionPath, ReadOnlyValueFormatBasicProperty);
            }
            else
            {
                ReadOnlyValueFormatBasic = Properties.Resources.ReadOnlyValueFormatBasic;
            }
        }

        private string ProperName(string name)
        {
            var s = name.Replace(" ", "");
            var regex = new Regex(@"[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Nl}\p{Mn}\p{Mc}\p{Cf}\p{Pc}\p{Lm}]");
            s = regex.Replace(s, "_");
            if (!char.IsLetter(name, 0))
            {
                s = string.Concat("_", s);
            }
            return s;
        }

        private async Task RefreshAsync(object parameter)
        {
            var endpointUrl = parameter as string;
            Trace.TraceInformation("BrowseAsync {0}", endpointUrl);
            try
            {
                BrowseStop();
                await _lock.WaitAsync(_cts.Token);
                NotifyPropertyChanged("IsLoading");
                try
                {
                    NamespaceItems.Clear();
                    if (_session != null)
                    {
                        _session.Dispose();
                        _session = null;
                    }
                    EndpointUrl = endpointUrl;
                    if (string.IsNullOrEmpty(endpointUrl))
                    {
                        return;
                    }
                    if (History.Count == 0 || History[0] != endpointUrl)
                    {
                        History.Insert(0, endpointUrl);
                    }
                    while (History.Count > 5) History.RemoveAt(5);
                    var root = new ReferenceDescriptionViewModel(new ReferenceDescription { DisplayName = "Objects", NodeId = new NodeId(Objects.ObjectsFolder, 0), NodeClass = NodeClass.Object, TypeDefinition = ObjectTypeIds.FolderType }, null, AccessLevels.None, EventNotifiers.None, null, LoadChildrenAsync);
                    NamespaceItems.Add(root);
                    root.IsExpanded = true;
                }
                finally
                {
                    _lock.Release();
                    NotifyPropertyChanged("IsLoading");
                }
            }
            catch (OperationCanceledException) { }
        }

        private void ResetSettings()
        {
            if (_store == null) return;
            if (_store.CollectionExists(CollectionPath))
            {
                _store.DeleteCollection(CollectionPath);
            }
            LoadSettings();
            LoadHistory();
        }

        private void SaveHistory()
        {
            if (_store == null) return;
            if (!_store.CollectionExists(CollectionPath))
            {
                _store.CreateCollection(CollectionPath);
            }
            _store.SetString(CollectionPath, HistoryProperty, string.Join("|", History));
        }

        private void SaveSettings()
        {
            if (_store == null) return;
            if (!_store.CollectionExists(CollectionPath))
            {
                _store.CreateCollection(CollectionPath);
            }
            _store.SetString(CollectionPath, ValueFormatCSharpProperty, ValueFormatCSharp);
            _store.SetString(CollectionPath, EventFormatCSharpProperty, EventFormatCSharp);
            _store.SetString(CollectionPath, MethodFormatCSharpProperty, MethodFormatCSharp);
            _store.SetString(CollectionPath, ReadOnlyValueFormatCSharpProperty, ReadOnlyValueFormatCSharp);
            _store.SetString(CollectionPath, ValueFormatBasicProperty, ValueFormatBasic);
            _store.SetString(CollectionPath, EventFormatBasicProperty, EventFormatBasic);
            _store.SetString(CollectionPath, MethodFormatBasicProperty, MethodFormatBasic);
            _store.SetString(CollectionPath, ReadOnlyValueFormatBasicProperty, ReadOnlyValueFormatBasic);
        }
    }
}